package com.android.test.net;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;

import android.os.Environment;
import android.util.Pair;

public class HttpCache {

    private final static int BUFSIZE = 8192;
    private final static String HttpCachePath = Environment
	    .getExternalStorageDirectory() + "";/* Constants.HTTP_CACHE_SUBDIR */

    private final static int MAX_CACHED_ITEM = 1024;
    private LinkedList<File> cachedItems = null;

    private static FilenameFilter getFileExtensionFilter(String extension) {
	final String _extension = extension;

	return new FilenameFilter() {
	    public boolean accept(File file, String name) {
		boolean ret = name.endsWith(_extension);
		return ret;
	    }
	};
    }

    public HttpCache() {

	File cacheFolder = new File(HttpCachePath);
	cacheFolder.mkdirs();

	class CachedItemsInit extends Thread {

	    public void run() {
		File cacheFolder = new File(HttpCachePath);

		// Create the file list in memory
		LinkedList<Pair<File, Long>> newCachedItems = new LinkedList<Pair<File, Long>>();
		File[] cachedFiles = cacheFolder
			.listFiles(getFileExtensionFilter(".meta"));

		if (cachedFiles == null) {
		    return;
		}

		for (int i = 0; i < cachedFiles.length; ++i) {
		    File f = cachedFiles[i];
		    Pair<File, Long> pair = new Pair<File, Long>(f,
			    f.lastModified());
		    newCachedItems.add(pair);
		    if (i % 100 == 0) {
			try {
			    Thread.sleep(250);
			} catch (InterruptedException e) {
			    e.printStackTrace();
			}
		    }
		}

		class FileTimeComparator implements
			Comparator<Pair<File, Long>> {
		    public int compare(Pair<File, Long> a, Pair<File, Long> b) {
			long distance = (a.second == null ? 0 : a.second)
				- (b.second == null ? 0 : b.second);
			if (distance > 0) {
			    return 1;
			} else if (distance < 0) {
			    return -1;
			} else {
			    return 0;
			}
		    }
		}
		Collections.sort(newCachedItems, new FileTimeComparator());

		LinkedList<File> tmpCachedItems = new LinkedList<File>();
		for (int i = 0, size = newCachedItems.size(); i < size; i++) {
		    Pair<File, Long> pair = newCachedItems.get(i);
		    if (pair != null && pair.first != null) {
			tmpCachedItems.add(pair.first);
		    }
		}
		cachedItems = tmpCachedItems;
		return;
	    }
	}

	CachedItemsInit cachedItemsInit = new CachedItemsInit();
	cachedItemsInit.setDaemon(true);
	cachedItemsInit.start();

	return;
    }

    public ByteBuffer getCachedUrlContent(String url, HttpClient httpClient,
	    HttpUriRequest request) throws ClientProtocolException, IOException {

	ByteBuffer result;
	String meta_url = null;
	String meta_time = null;

	String filename = HttpCachePath + "/"
		+ Integer.toHexString(url.hashCode());
	String filename_meta = filename + ".meta";
	String filename_bin = filename + ".bin";

	// Find the file on SD card
	File file_meta = new File(filename_meta);
	if (file_meta.exists()) {
	    FileInputStream fis = new FileInputStream(file_meta);
	    InputStreamReader isr = new InputStreamReader(fis);
	    BufferedReader br = new BufferedReader(isr);
	    meta_url = br.readLine();
	    meta_time = br.readLine();
	    br.close();
	    isr.close();
	    fis.close();
	}

	// Add If-Modified-Since header
	if (meta_url != null && meta_url.equals(url) && meta_time != null) {
	    request.addHeader("If-Modified-Since", meta_time);
	}

	// Send the http request
	HttpResponse response = httpClient.execute(request);
	if (response.getStatusLine().getStatusCode() == 304) {
	    // Http status code 304, not modified, just load the file from disk.
	    try {
		FileInputStream fis = new FileInputStream(filename_bin);
		BufferedInputStream bis = new BufferedInputStream(fis);
		ByteArrayOutputStream os = new ByteArrayOutputStream(BUFSIZE);

		byte buffer[] = new byte[BUFSIZE];
		int count;
		while ((count = bis.read(buffer, 0, buffer.length)) != -1) {
		    os.write(buffer, 0, count);
		}
		result = ByteBuffer.wrap(os.toByteArray());
		bis.close();
		os.close();
		return result;
	    } catch (Exception e) {
		// Anything wrong, remove the header If-Modified-Since and
		// re-send the request
		request.removeHeaders("If-Modified-Since");
		response = httpClient.execute(request);
	    }
	}

	// Read bytes from HTTP server
	InputStream is = response.getEntity().getContent();
	ByteArrayOutputStream os = new ByteArrayOutputStream(BUFSIZE);

	byte buffer[] = new byte[BUFSIZE];
	int count;
	while ((count = is.read(buffer, 0, buffer.length)) != -1) {
	    os.write(buffer, 0, count);
	}
	result = ByteBuffer.wrap(os.toByteArray());
	is.close();
	os.close();

	// Store the bytes to disk.
	try {
	    FileWriter fw = new FileWriter(filename_meta);
	    BufferedWriter bw = new BufferedWriter(fw);
	    bw.write(url + "\n");
	    bw.write(response.getHeaders("Last-Modified")[0].getValue() + "\n");
	    bw.close();
	    fw.close();

	    FileOutputStream fos = new FileOutputStream(filename_bin);
	    fos.write(result.array());
	    fos.close();

	    // Check if the disk is overloaded and remove some files if
	    // necessary
	    if (cachedItems != null) {
		synchronized (cachedItems) {
		    cachedItems.add(new File(filename_meta));
		    int cacheSize = cachedItems.size();
		    if (cacheSize > MAX_CACHED_ITEM) {
			for (int i = 0; i < cacheSize - MAX_CACHED_ITEM; ++i) {
			    // Remove items from the head of the list
			    File file = cachedItems.removeFirst();
			    // Log.d("Remove cache file", file.lastModified() +
			    // " - " + file.getAbsolutePath());
			    file.delete(); // This is the meta file
			    String remove_filename = file.getAbsolutePath();
			    remove_filename = remove_filename.substring(0,
				    remove_filename.length() - 5) + ".bin";
			    file = new File(remove_filename);
			    file.delete(); // This is the bin file
			}
		    }
		}
	    }
	} catch (Exception e) {
	    // If there's anything wrong, remove both meta and bin files.
	    new File(filename_meta).delete();
	    new File(filename_bin).delete();
	}
	return result;
    }
}
